package cn.okcode.slowjson.parser;

import cn.okcode.slowjson.ast.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * java bean生成类
 * 根据json生成java类
 * <p>
 * TODO:有很多问题~
 */
public class JavaBeanGenHelper {

    public static final String PACKAGE = "org.slowjson.demo";

    private final AbstractNode<?> node;
    private final String className;

    private final StringBuilder out = new StringBuilder();

    private final List<ClazzInfo> objectNodeRecords = new ArrayList<>();

    public JavaBeanGenHelper(AbstractNode<?> node, String className) {
        this.node = node;
        this.className = className;
    }

    public String createJavaBean() {
        if (node instanceof ObjectNode) {
            process((ObjectNode) node, className);
        } else if (node instanceof ArrayNode) {
            process((ArrayNode) node, className);
        }
        return build();
    }

    private void process(ArrayNode node, String className) {
        if (node.getElements().size() > 0) {
            AbstractNode<?> eleNode = node.getElements().get(0);
            if (eleNode instanceof ObjectNode) {
                process((ObjectNode) eleNode, className);
            } else if (eleNode instanceof ArrayNode) {
                process((ArrayNode) eleNode, className);
            }
        }
    }

    private String convertClassName(String propName) {
        return propName.substring(0, 1).toUpperCase() + propName.substring(1);
    }

    private void process(ObjectNode node, String className) {
        node.getProperties().forEach((key, valueNode) -> {
            String propName = key.getValue();
            String _className = convertClassName(propName);
            if (valueNode instanceof ObjectNode) {
                process((ObjectNode) valueNode, _className);
            } else if (valueNode instanceof ArrayNode) {
                process((ArrayNode) valueNode, _className);
            }
        });

        recordObjectNode(node, className);
    }

    private void recordObjectNode(ObjectNode node, String objectName) {
        boolean removed = false;
        boolean found = false;
        int index = 0;
        for (ClazzInfo clazzInfo : objectNodeRecords) {
            if (objectNodeEquals(clazzInfo.node, node)) {
                found = true;
                if (clazzInfo.node.getProperties().size() < node.getProperties().size()) {
                    removed = true;
                }

                if (node.getProperties().containsValue(clazzInfo.node.getParent())) {
                    //新来的节点是旧节点的父类，则使用新来的节点命名
                    objectNodeRecords.get(index).className = objectName;
                }
                break;
            }
            index++;
        }

        if (removed) {
            objectNodeRecords.remove(index);
            objectNodeRecords.add(new ClazzInfo(objectName, node));
        }

        if (!found) {
            objectNodeRecords.add(new ClazzInfo(objectName, node));
        }
    }

    public boolean objectNodeEquals(ObjectNode node1, ObjectNode node2) {
        Map<PropertyNode, AbstractNode<?>> smallObject;
        Map<PropertyNode, AbstractNode<?>> largeObject;

        if (node1.getProperties().size() < node2.getProperties().size()) {
            smallObject = node1.getProperties();
            largeObject = node2.getProperties();
        } else {
            smallObject = node2.getProperties();
            largeObject = node1.getProperties();
        }

        for (Map.Entry<PropertyNode, AbstractNode<?>> smallEntry : smallObject.entrySet()) {
            String name = smallEntry.getKey().getValue();
            String type = smallEntry.getValue().getClass().getName();
            boolean isEqual = false;
            for (Map.Entry<PropertyNode, AbstractNode<?>> largeEntry : largeObject.entrySet()) {
                String name1 = largeEntry.getKey().getValue();
                String type1 = largeEntry.getValue().getClass().getName();

                if (name.equals(name1) && type.equals(type1)) {
                    //如果值是对象类型 则需要判断具类型是否一致
                    if (smallEntry.getValue() instanceof ObjectNode) {
                        if (objectNodeEquals((ObjectNode) smallEntry.getValue(), (ObjectNode) largeEntry.getValue())) {
                            isEqual = true;
                            break;
                        }
                    } else {
                        isEqual = true;
                        break;
                    }
                }
            }
            if (!isEqual) {
                return false;
            }
        }
        return true;
    }

    private String build() {
        //out.append("package ").append(PACKAGE).append(System.lineSeparator());

        for (ClazzInfo clazz : objectNodeRecords) {
            out.append(System.lineSeparator())
                    .append(System.lineSeparator())
                    .append("public class ")
                    .append(clazz.className)
                    .append(" {")
                    .append(System.lineSeparator());

            for (Map.Entry<PropertyNode, AbstractNode<?>> node : clazz.node.getProperties().entrySet()) {
                String propName = node.getKey().getValue();
                String _className = convertClassName(propName);
                out.append("    private ");

                AbstractNode<?> valueNode = node.getValue();

                if (valueNode instanceof ObjectNode) {
                    out.append(_className);
                } else if (valueNode instanceof ArrayNode) {
                    ArrayNode arrayValueNode = (ArrayNode) valueNode;
                    String eleTypeName = "";
                    if (arrayValueNode.getElements().size() > 0) {
                        AbstractNode<?> eleNode = arrayValueNode.getElements().get(0);
                        eleTypeName = getTypeName(eleNode);
                    }
                    if (!eleTypeName.equals("")) {
                        out.append(eleTypeName).append("[]");
                    } else {
                        out.append(_className).append("[]");
                    }
                } else {
                    out.append(getTypeName(valueNode));
                }

                out.append(" ")
                        .append(propName)
                        .append(" ")
                        .append(System.lineSeparator());
            }
            out.append("}").append(System.lineSeparator());
        }

        return out.toString();
    }

    private String getTypeName(AbstractNode<?> node) {
        if (node instanceof StringNode) {
            return "String";
        } else if (node instanceof NumberNode) {
            return "Double";
        } else if (node instanceof BoolNode) {
            return "Boolean";
        } else if (node instanceof NullNode) {
            return "Object";
        }
        return "";
    }

    static class ClazzInfo {
        String className;
        ObjectNode node;

        public ClazzInfo(String className, ObjectNode node) {
            this.className = className;
            this.node = node;
        }
    }
}
